package main

import (
	"fmt"
	"github.com/henrylee2cn/faygo"
	"io/ioutil"
	"net/http"
	"regexp"
	"time"
	"github.com/xormplus/xorm"
	_ "github.com/go-sql-driver/mysql"
	"math/rand"
)

var (
	ptnIndexItem  = regexp.MustCompile(`<a href="((http|/).+?)".+>(.+)</a>`)
	ptnIndexImg   = regexp.MustCompile(`src="(.*?(\.jpg|\.png))`)
	ptnIndexVideo = regexp.MustCompile(`src="(.*?(\.mkv|\.mp4|\.wav|\.flv))`)
)

type IndexItem struct {
	Url   string
	Title string
}

func main() {
	go ORM()
	var app = faygo.New("XL", "V1.0")
	app.Route(
		app.NewNamedGET("爬虫", "/deep", deep),
		app.NewNamedGET("主页", "/", index),
		app.NewNamedGET("主页", "/index", index),
		app.NewNamedGET("主页", "/home", home),
		app.NewNamedPOST("登录", "/login", &Admin{}),
		app.NewNamedGET("归档", "/Arch", Arch),
		app.NewNamedGET("获取记录-HTML5", "/record", record),
		app.NewNamedGET("操作记录", "/motion", &Mydata{}),
		app.NewNamedGET("获取采集记录", "/getall", &GetAllData{1, ""}),
		app.NewNamedPOST("删除Data", "/deletedata", &deleteData{}),
		app.NewNamedPOST("清空Data", "/deleteall", &deleteall{}),


	)

	faygo.Run()
}

func SubString(str string, begin, length int) (substr string) {
	// 将字符串的转换成[]rune
	rs := []rune(str)
	lth := len(rs)
	// 简单的越界判断
	if begin < 0 {
		begin = 0
	}
	if begin >= lth {
		begin = lth
	}
	end := begin + length
	if end > lth {
		end = lth
	}
	// 返回子串
	return string(rs[begin:end])
}

var index = faygo.HandlerFunc(func(ctx *faygo.Context) error {
	if ctx.GetSession("user") == nil {
		fmt.Println("尚未登录的用户")
		return home(ctx)
	}
	return ctx.Render(200, "view/index.html", faygo.Map{})
})

func (tmpObj *Mydata) Serve(ctx *faygo.Context) error {
	fmt.Println("Option:", ctx.Param("option"))
	var option = ctx.Param("option")
	if option == "RRR" {
		var obj Mydata
		engine.ID(tmpObj.Id).Get(&obj)
		fmt.Println("返回的对象", obj)
		return ctx.Render(200, "view/details.html", faygo.Map{
			"obj": obj,
		})
	} else if option == "DDD" {
		engine.ID(tmpObj.Id).Delete(tmpObj)
		return ctx.JSON(200, faygo.Map{"message": "删除成功!"})
	}
	return ctx.JSON(200, faygo.Map{"error": "非法操作!"})
}

func (tmpObj *Admin) Serve(ctx *faygo.Context) error {
	exist, err := engine.Exist(tmpObj)
	if exist && err == nil {
		ctx.SetSession("user", tmpObj.Username)
		return ctx.Render(200, "view/index.html", faygo.Map{
			"obj": tmpObj,
		})
	}
	return ctx.Render(200, "view/login.html", faygo.Map{})
}

var record = faygo.HandlerFunc(func(ctx *faygo.Context) error {
	var list []Mydata
	engine.Find(&list)
	return ctx.Render(200, "view/record.html", faygo.Map{
		"list": list,
	})
})

var home = faygo.HandlerFunc(func(ctx *faygo.Context) error {
	ctx.DestroySession()
	return ctx.Render(200, "view/login.html", faygo.Map{})
})

var deep = faygo.HandlerFunc(func(ctx *faygo.Context) error {
	var str = ctx.Param("url")
	if str[len(str)-1] == '/' {
		str = SubString(str, 0, len(str)-1)
	}
	fmt.Println("获取到的URL是:", str)
	content, stat := Get(str)
	if stat != 200 {
		fmt.Println("无效的URL,无法进行数据挖掘")
		return ctx.JSON(200, faygo.Map{"error": "无效的URL,无法进行数据挖掘"})
	}
	var linkList = FindLinks(str, content)
	var imgList = FindImages(str, content)
	var vidList = FindVideoes(str, content)
	Tmpdata = Mydata{0, str, linkList, imgList, vidList, ""}
	return ctx.JSON(200, faygo.Map{
		"link":  linkList,
		"img":   imgList,
		"video": vidList,
	})
})

type Mydata struct {
	Id          int64       `param:"<in:query><required><range:0:9999><err:ID不能为空!>"`
	Distance    string
	Link        []IndexItem `xorm:"LONGBLOB"`
	Img         []IndexItem `xorm:"LONGBLOB"`
	Video       []IndexItem `xorm:"LONGBLOB"`
	Create_time string      `xorm:"TIMESTAMP"`
}

type Data struct {
	Id    int64
	Kind  int
	Name  string
	Url   string
	Ctime time.Time `xorm:"created"`
}

var Tmpdata Mydata

var Arch = faygo.HandlerFunc(func(ctx *faygo.Context) error {
	row, err := engine.Omit("id,create_time").Insert(Tmpdata)
	if row == 1 && err == nil {
		Tmpdata = Mydata{}
		return ctx.JSON(200, faygo.Map{
			"message": "成功保存到数据库!",
		})
	} else {
		return ctx.JSON(200, faygo.Map{
			"message": "保存失败!",
			"error":   err.Error(),
		})
	}
})

func Get(url string) (content string, statusCode int) {
	resp, err1 := http.Get(url)
	if err1 != nil {
		statusCode = -100
		return
	}
	defer resp.Body.Close()
	data, err2 := ioutil.ReadAll(resp.Body)
	if err2 != nil {
		statusCode = -200
		return
	}
	statusCode = resp.StatusCode
	content = string(data)
	return
}

func NewData(kind int, url, name string) {
	go func() {
		var data Data
		data.Kind = kind
		data.Name = name
		data.Url = url
		engine.Insert(&data)
	}()
}

func FindLinks(baseurl string, content string) (index []IndexItem) {
	matches := ptnIndexItem.FindAllStringSubmatch(content, 10000)
	index = make([]IndexItem, len(matches))
	for i, item := range matches {
		var str = item[1]
		if len(str) > 0 {
			if str[0] == '/' {
				str = baseurl + str
			}
		}
		index[i] = IndexItem{str, item[3]}
		NewData(1, str, item[3])
	}
	fmt.Println("获取的链接数:", len(matches))
	return index
}

func FindImages(baseurl string, content string) (index []IndexItem) {
	matches := ptnIndexImg.FindAllStringSubmatch(content, 10000)
	index = make([]IndexItem, len(matches))
	for i, item := range matches {
		var str = item[1]
		if len(str) > 0 {
			if str[0] == '/' {
				str = baseurl + str
			}
		}
		index[i] = IndexItem{str, ""}
		NewData(2, str, GetRandomString())
	}
	fmt.Println("获取的图片数:", len(matches))
	return index
}

func FindVideoes(baseurl string, content string) (index []IndexItem) {
	matches := ptnIndexVideo.FindAllStringSubmatch(content, 10000)
	index = make([]IndexItem, len(matches))
	for i, item := range matches {
		var str = item[1]
		if len(str) > 0 {
			if str[0] == '/' {
				str = baseurl + str
			}
		}
		index[i] = IndexItem{str, ""}
		NewData(3, str, GetRandomString())
	}
	fmt.Println("获取的视频数:", len(matches))
	return index
}

func GetRandomString(length ...int) string {
	str := "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
	bytes := []byte(str)
	var result []byte
	r := rand.New(rand.NewSource(time.Now().UnixNano()))
	var l = 10
	if len(length) > 0 {
		if length[0] > 0 {
			l = length[0]
		}
	}
	for i := 0; i < l; i++ {
		result = append(result, bytes[r.Intn(len(bytes))])
	}
	return string(result)
}

type Admin struct {
	Username    string `param:"<in:formData><required><len:5:20><err:账号不能为空!>"`
	Password    string `param:"<in:formData><required><len:5:20><err:密码不能为空!>"`
	Create_time string `xorm:"TIMESTAMP DEFAULT 'CURRENT_TIMESTAMP'"`
}

var engine *xorm.Engine

func ORM() {
	start := time.Now()
	var SQLerr error
	engine, SQLerr = xorm.NewEngine("mysql", "root:okayokay@tcp(www.deeokay.top:3306)/XL?charset=utf8")
	if SQLerr != nil {
		fmt.Println(SQLerr.Error())
	}
	objetArr := []interface{}{
		new(Admin),
		new(Mydata),
		new(Data),
	}
	var finish = 0
	var channel = make(chan int)
	defer close(channel)
	for i, obj := range objetArr {
		go func(index int, object interface{}) {
			syncErr := engine.Sync2(object)
			if syncErr != nil {
				//fmt.Println(syncErr)
			} else {
				fmt.Printf("ORM同步第%d个模型完成%v\n", index, object)
			}
			finish++
			if finish == len(objetArr) {
				channel <- 0
			}
		}(i, obj)
	}
	<-channel
	fmt.Println("ORM用时:", time.Since(start))
	fmt.Println("---> 1.爬虫系统已成功启动")
	fmt.Println("---> 2.请在浏览器中输入: 127.0.0.1:8082 即可进入爬虫系统")
}

type GetAllData struct {
	Kind    int    `param:"<in:query><range:1:3>"`
	Keyword string `param:"<in:query><legth:1:20>"`
}

func (tmp *GetAllData) Serve(ctx *faygo.Context) error {
	var list []Data
	engine.ShowSQL(true)
	fmt.Println("show key word", tmp.Keyword)
	switch tmp.Kind {
	case 1:
		engine.Where("name like ?", fmt.Sprint("%", tmp.Keyword, "%")).Find(&list, &Data{Kind: 1})
	case 2:
		engine.Find(&list, &Data{Kind: 2})
	case 3:
		engine.Find(&list, &Data{Kind: 3})
	default:
		break
	}
	//fmt.Println("输出检查")
	var countlist = make(map[string]int64)
	for _, k := range KW_list {

		row, _ := engine.Where("name like ?", fmt.Sprint("%", k, "%")).Count(new(Data))
		countlist[k] = row

	}
	return ctx.Render(200, "view/analyze.html", faygo.Map{
		"list":    list,
		"keylist": countlist,
	})
}

var KW_list = []string{
	"教程",
	"实战",
	"学习",
	"Java",
	"C++",
	"Pyhton",
}

type deleteData struct {
	Id int64 `param:"<in:formData><required><nozero>"`
}

func (tmp *deleteData) Serve(ctx *faygo.Context) error {
	row, err := engine.ID(tmp.Id).Delete(new(Data))

	return ctx.JSON(200, faygo.Map{
		"row": row,
		"err": err,
	})
}

type deleteall struct {
	Kind int `param:"<in:formData><range:1:3>"`
}

func (tmp *deleteall) Serve(ctx *faygo.Context) error {
	row, err := engine.Delete(&Data{Kind: tmp.Kind})
	return ctx.JSON(200, faygo.Map{
		"row":   row,
		"error": err,
	})
}
